package com.wind;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JSON {

	private String type = "root";
	private String name;
	private JSON parent = null;
	private String stringValue;
	
	private List<JSON> children = new ArrayList<JSON>();
	private Map<String,JSON> childrenByName = new HashMap<String,JSON>();

	private void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

	private JSON getParent() {
		return this.parent;
	}
	
	public void setStringValue(String stringValue) {
		this.stringValue = stringValue;
	}

	public String getStringValue() {
		return this.stringValue;
	}

	public List<JSON> getChildren() {
		return this.children;
	}

	private String getType() {
		return this.type;
	}
	
	public boolean isString() {
		return this.type.equals("string");
	}

	private void setType(String type) {
		this.type = type;
	}

	public JSON get(String name) {
		JSON child = this.childrenByName.get(name);
		return child;
	}
	
	public String getString(String name) {
		String string = null;
		JSON child = get(name);
		if(child!=null) {
			string = child.getStringValue();
		}
		return string;
	}

	public JSON addObject(String name) {
		JSON child = new JSON();
		child.setName(name);
		child.setType("object");
		child.parent = this;
		this.children.add(child);
		this.childrenByName.put(name, child);
		return child;
	}

	public JSON addArray(String name) {
		JSON child = new JSON();
		child.setName(name);
		child.setType("array");
		child.parent = this;
		getChildren().add(child);
		return child;
	}

	public JSON addArrayElement() {
		JSON child = new JSON();
		child.setType("arrayElement");
		child.parent = this;
		getChildren().add(child);
		return child;
	}

	public JSON addAttribute(String name, String value) {
		JSON child = new JSON();
		child.setName(name);
		child.setStringValue(value);
		child.setType("attribute");
		child.parent = this;
		this.children.add(child);
		this.childrenByName.put(name, child);
		return child;
	}

	public String toString() {
		StringBuffer json = new StringBuffer();

		if(this.getType().equals("root")) {
			json.append("(");
			json.append("{");
			for(int i=0; i<this.getChildren().size(); i++) {
				JSON child = this.getChildren().get(i);
				json.append(child.toString());
				if ((i + 1) < this.getChildren().size()) {
					json.append(",");
				}
			}
			json.append("}");
			json.append(")");
		}

		if(this.getType().equals("object")) {
			json.append("\"" + this.getName() + "\"");
			json.append(":");
			json.append("{");
			for(int i=0; i<this.getChildren().size(); i++) {
				JSON child = this.getChildren().get(i);
				json.append(child.toString());
				if ((i + 1) < this.getChildren().size()) {
					json.append(",");
				}
			}
			json.append("}");
		}
		
		if(this.getType().equals("array")) {
			json.append("\"" + this.getName() + "\"");
			json.append(":");
			json.append("[");
			for(int i=0; i<this.getChildren().size(); i++) {
				JSON child = this.getChildren().get(i);
				json.append(child.toString());
				if ((i + 1) < this.getChildren().size()) {
					json.append(",");
				}
			}
			json.append("]");
		}
		
		if(this.getType().equals("arrayElement")) {
			json.append("{");
			for(int i=0; i<this.getChildren().size(); i++) {
				JSON child = this.getChildren().get(i);
				json.append(child.toString());
				if ((i + 1) < this.getChildren().size()) {
					json.append(",");
				}
			}
			json.append("}");
		}

		if(this.getType().equals("string")) {
			String stringValue = this.getStringValue();
			if(stringValue!=null) {
				//stringValue = StringEscapeUtils.escapeJavaScript(value);
				json.append("\"" + stringValue + "\"");
			} else {
				json.append("\"\"");
			}
		}

		if(this.getType().equals("attribute")) {
			json.append("\"" + this.getName() + "\"");
			json.append(":");
			String stringValue = this.getStringValue();
			if(stringValue!=null) {
				//stringValue = StringEscapeUtils.escapeJavaScript(value);
				json.append("\"" + stringValue + "\"");
			} else {
				json.append("\"\"");
			}
		}

		return json.toString();
	}
	
	
	private static final int JSON_PARSE_STATE_START = 0;
	private static final int JSON_PARSE_STATE_ERROR = 1;
	private static final int JSON_PARSE_STATE_FINISH = 2;
	private static final int JSON_PARSE_STATE_INSIDE_OBJECT = 3;
	private static final int JSON_PARSE_STATE_INSIDE_OBJECT_NAME = 4;
	private static final int JSON_PARSE_STATE_FINISHED_OBJECT_NAME = 5;
	private static final int JSON_PARSE_STATE_INSIDE_OBJECT_VALUE = 6;
	private static final int JSON_PARSE_STATE_INSIDE_ARRAY = 7;
	private static final int JSON_PARSE_STATE_INSIDE_QUOTED_STRING = 8;
	private static final int JSON_PARSE_STATE_INSIDE_NUMBER = 9;
	
	public static JSON parse(String str) {
		JSON root = new JSON();
		
		if(str!=null) {
			StringBuilder token = new StringBuilder();
			JSON current = root;
			int state = JSON_PARSE_STATE_START;
			int i = 0;
			while(state!=JSON_PARSE_STATE_ERROR && state!=JSON_PARSE_STATE_FINISH && i<str.length()) {
				char c = str.charAt(i);
				switch(state) {
				case JSON_PARSE_STATE_START:
					if(c=='{') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
					} else {
						state = JSON_PARSE_STATE_ERROR;
					}
					break;
				case JSON_PARSE_STATE_INSIDE_OBJECT:
					if(c=='"') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT_NAME;
						token = new StringBuilder();
					} else if(c==' ') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
					} else if(c=='}') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
						current = current.getParent();
					} else if(c==']') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
						current = current.getParent();
					} else if(c==',') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
					} else {
						state = JSON_PARSE_STATE_ERROR;
					}
					break;
				case JSON_PARSE_STATE_INSIDE_OBJECT_NAME:
					if(c=='"') {
						state = JSON_PARSE_STATE_FINISHED_OBJECT_NAME;
						current = current.addObject(token.toString());
					} else {
						token.append(c);
					}
					break;
				case JSON_PARSE_STATE_FINISHED_OBJECT_NAME:
					if(c==':') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT_VALUE;
					} else {
						state = JSON_PARSE_STATE_ERROR;
					}
					break;
				case JSON_PARSE_STATE_INSIDE_OBJECT_VALUE:
					if(c=='{') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
					} else if(c=='}') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
						current = current.getParent();
					} else if(c=='[') {
						state = JSON_PARSE_STATE_INSIDE_ARRAY;
						current.setType("array");
					} else if(c=='"') {
						state = JSON_PARSE_STATE_INSIDE_QUOTED_STRING;
						current.setType("string");
						token = new StringBuilder();
					} else if(c==',') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
					} else if(c=='}') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
					} else if(c==' ') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT_VALUE;
					} else {
						if(Character.isDigit(c)) {
							state = JSON_PARSE_STATE_INSIDE_NUMBER;
							current.setType("string");
							token = new StringBuilder();
							token.append(c);
						} else {
							state = JSON_PARSE_STATE_ERROR;
						}
					}
					break;
				case JSON_PARSE_STATE_INSIDE_QUOTED_STRING:
					if(c=='"') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT_VALUE;
						current.setStringValue(token.toString());
						current = current.getParent();
					} else {
						token.append(c);
					}
					break;
				case JSON_PARSE_STATE_INSIDE_NUMBER:
					if(Character.isDigit(c)) {
						token.append(c);
					} else {
						state = JSON_PARSE_STATE_INSIDE_OBJECT_VALUE;
						current.setStringValue(token.toString());
						current = current.getParent();
						i--;
					}
					break;
				case JSON_PARSE_STATE_INSIDE_ARRAY:
					if(c=='{') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
						current = current.addArrayElement();
					} else if(c==']') {
						state = JSON_PARSE_STATE_INSIDE_OBJECT;
						current = current.getParent();
					} else {
						state = JSON_PARSE_STATE_ERROR;
					}
					break;
				default:
					state = JSON_PARSE_STATE_ERROR;
				}
				i++;
			}
			
			if(state==JSON_PARSE_STATE_ERROR) {
				throw new RuntimeException("Error parsing JSON string");
			}
		}
		
		return root;
	}
}
